Youtube Videos:
* https://www.youtube.com/watch?v=rq8cL2XMM5M&index=3&list=PL-osiE80TeTsqhIuOqKhwlXsIBIdSeYtc
* https://www.youtube.com/watch?v=RSl87lqOXDE&index=4&list=PL-osiE80TeTsqhIuOqKhwlXsIBIdSeYtc
Online References:
* https://jeffknupp.com/blog/2017/03/27/improve-your-python-python-classes-and-object-oriented-programming/
* https://dbader.org/blog/abstract-base-classes-in-python
In [14]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
def __init__(self, fname, lname):
self.fname = fname
self.lname = lname
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
emp1 = Employee('Sri', 'Paladugu')
emp2 = Employee('Dhruv', 'Paladugu')
print( emp1.get_fullname() )
print( Employee.emp_count )
# Trobule ensues when you treat class variables as instance attribute.
# What the interpreter does in this case is, it creates an instance attribute with the same name and assigns to it.
# The class variable still remains intact with old value.
emp1.company = 'Verily'
print(emp1.company)
print(emp1.get_company())
print(emp2.company)
print(emp2.email)
In [15]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname):
self.fname = fname
self.lname = lname
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
@classmethod
def set_raise_amt(cls, amount):
cls.raise_amount = amount
emp1 = Employee('Sri', 'Paladugu')
emp2 = Employee('Dhruv', 'Paladugu')
Employee.set_raise_amt(1.05)
print(Employee.raise_amount)
print(emp1.raise_amount)
print(emp2.raise_amount)
In [16]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
@classmethod
def set_raise_amt(cls, amount):
cls.raise_amount = amount
@classmethod
def from_string(cls, emp_str):
fname, lname, salary = emp_str.split("-")
return cls(fname, lname, salary)
new_emp = Employee.from_string("Pradeep-Koganti-10000")
print(new_emp.email)
In [17]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
@classmethod
def set_raise_amt(cls, amount):
cls.raise_amount = amount
@classmethod
def from_string(cls, emp_str):
fname, lname, salary = emp_str.split("-")
return cls(fname, lname, salary)
@staticmethod
def is_workday(day):
if day.weekday() == 5 or day.weekday() == 6:
return False
else:
return True
import datetime
my_date = datetime.date(2016, 7, 10)
print(Employee.is_workday(my_date))
In [19]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
def apply_raise(self):
self.salary = self.salary * self.raise_amount
class Developer(Employee):
pass
dev1 = Developer('Sri', 'Paladugu', 1000)
print(dev1.get_fullname())
print(help(Developer)) # This command prints the Method resolution order.
# Indicating the order in which the interpreter is going to look for methods.
Now what if you want Developer's raise_amount to be 10%?
In [20]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
def apply_raise(self):
self.salary = self.salary * self.raise_amount
class Developer(Employee):
raise_amount = 1.10
dev1 = Developer('Sri', 'Paladugu', 1000)
dev1.apply_raise()
print(dev1.salary)
Now what if we want the Developer class to have an extra attribute like prog_lang?
In [21]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
def apply_raise(self):
self.salary = self.salary * self.raise_amount
class Developer(Employee):
raise_amount = 1.10
def __init__(self, fname, lname, salary, prog_lang):
super().__init__(fname, lname, salary)
# or you can also use the following syntax
# Employee.__init__(self, fname, lname, salary)
self.prog_lang = prog_lang
dev1 = Developer('Sri', 'Paladugu', 1000, 'Python')
print(dev1.get_fullname())
print(dev1.prog_lang)
Gotcha - Mutable default arguments
In [40]:
class Employee:
emp_count = 0 # Class Variable
company = 'Google' # Class Variable
raise_amount = 1.04
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
Employee.emp_count += 1
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def get_company(self):
return 'Company Name is: {}'.format(Employee.company)
def apply_raise(self):
self.salary = self.salary * self.raise_amount
class Developer(Employee):
raise_amount = 1.10
def __init__(self, fname, lname, salary, prog_lang):
super().__init__(fname, lname, salary)
# or you can also use the following syntax
# Employee.__init__(self, fname, lname, salary)
self.prog_lang = prog_lang
class Manager(Employee):
def __init__(self, fname, lname, salary, employees = None): # Use None as default not empty list []
super().__init__(fname, lname, salary)
if employees is None:
self.employees = []
else:
self.employees = employees
def add_employee(self, emp):
if emp not in self.employees:
self.employees.append(emp)
def remove_employee(self, emp):
if emp in self.employees:
self.employees.remove(emp)
def print_emps(self):
for emp in self.employees:
print('--->', emp.get_fullname())
dev_1 = Developer('Sri', 'Paladugu', 1000, 'Python')
dev_2 = Developer('Dhruv', 'Paladugu', 2000, 'Java')
mgr_1 = Manager('Sue', 'Smith', 9000, [dev_1])
print(mgr_1.email)
print(mgr_1.print_emps())
mgr_1.add_employee(dev_2)
print(mgr_1.print_emps())
print('Is dev_1 an instance of Developer: ', isinstance(dev_1, Developer))
print('Is dev_1 an instance of Employee: ', isinstance(dev_1, Employee))
print('Is Developer an Subclass of Developer: ', issubclass(Developer, Developer))
print('Is Developer an Subclass of Employee: ', issubclass(Developer, Employee))
Dunder methods:
__repr____str__
In [47]:
class Employee:
company = 'Google'
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
def __repr__(self): # For other developers
return "Employee('{}','{}','{}')".format(self.fname, self.lname, self.salary)
def __str__(self): # For end user
return '{} - {}'.format(self.get_fullname(), self.email)
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
emp1 = Employee('Sri', 'Paladugu', 5000)
print(emp1)
print(repr(emp1))
__add____len__
In [53]:
# if you do: 1 + 2 internally the interpreter calls the dunder method __add__
print(int.__add__(1,2))
# Similarly # if you do: [2,3] + [4,5] internally the interpreter calls the dunder method __add__
print(list.__add__([2,3],[4,5]))
print('Paladugu'.__len__()) # This is same as len('Paladugu')
In [51]:
class Employee:
company = 'Google'
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
self.email = self.fname + '.' + self.lname + '@' + self.company + '.com'
def __repr__(self): # For other developers
return "Employee('{}','{}','{}')".format(self.fname, self.lname, self.salary)
def __str__(self): # For end user
return '{} - {}'.format(self.get_fullname(), self.email)
def get_fullname(self):
return '{} {}'.format(self.fname, self.lname)
def __add__(self, other):
return self.salary + other.salary
def __len__(self):
return len(self.get_fullname())
emp1 = Employee('Sri', 'Paladugu', 5000)
emp2 = Employee('Dhruv', 'Paladugu', 5000)
print(emp1 + emp2)
print(len(emp1))
In [56]:
class Employee:
company = 'Google'
def __init__(self, fname, lname, salary):
self.fname = fname
self.lname = lname
self.salary = salary
@property
def email(self):
return '{}.{}@{}.com'.format(self.fname, self.lname, self.company)
@property
def fullname(self):
return '{} {}'.format(self.fname, self.lname)
@fullname.setter
def fullname(self, name):
first, last = name.split(' ')
self.fname = first
self.lname = last
@fullname.deleter
def fullname(self):
print('Delete Name!')
self.fname = None
self.lname = None
emp1 = Employee('Sri', 'Paladugu', 5000)
print(emp1.email)
print(emp1.fullname)
emp1.fullname = 'Ramki Paladugu'
print(emp1.email)
del emp1.fullname
print(emp1.email)
What are Abstract Base Classes good for? A while ago I had a discussion about which pattern to use for implementing a maintainable class hierarchy in Python. More specifically, the goal was to define a simple class hierarchy for a service backend in the most programmer-friendly and maintainable way.
There was a BaseService that defines a common interface and several concrete implementations that do different things but all provide the same interface (MockService, RealService, and so on). To make this relationship explicit the concrete implementations all subclass BaseService.
To be as maintainable and programmer-friendly as possible the idea was to make sure that:
In [4]:
from abc import ABCMeta, abstractmethod
class Base(metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
class Concrete(Base):
def foo(self):
pass
# We forget to declare bar()
c = Concrete()